#ifndef USART_H_
#define USART_H_

#include "stm32f10x_conf.h"
#include "stm32lib/misc.h"
#include "stm32lib/stm32f10x_rcc.h"
#include "stm32lib/stm32f10x_gpio.h"
#include "stm32lib/stm32f10x_usart.h"

#include "queue.h"

#define USART_TEMPLATE         int usart_idx, int usart_base, int baudrate, int tx_GPIOx, int tx_GPIO_Pin, int rx_GPIOx, int rx_GPIO_Pin  
#define USART_TEMPLATE_PARAMS      usart_idx,     usart_base,     baudrate,     tx_GPIOx,     tx_GPIO_Pin,     rx_GPIOx,     rx_GPIO_Pin  

extern class UsartInterrupt
{
  public:
    virtual void usartInterrupt(char data) {}
} *interrupt_target[5];


template <USART_TEMPLATE>
class Usart
{
  public:
    Usart()
    {
      interrupt_target[usart_idx] = 0;
    }

    ~Usart(void)
    {
      interrupt_target[usart_idx] = 0;
    }

    void enableRxInterrupt(void)
    {
      NVIC_InitTypeDef NVIC_InitStructure;      
      switch(usart_idx) {
        case 1:
          NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
          break;
        case 2:
          NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
          break;
        case 3:
          NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
          break;
      }
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&NVIC_InitStructure);
    }

    void disableRxInterrupt(void)
    {
      NVIC_InitTypeDef NVIC_InitStructure;
      switch(usart_idx) {
        case 1:
          NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
          break;
        case 2:
          NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
          break;
        case 3:
          NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
          break;
      }
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
      NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
      NVIC_Init(&NVIC_InitStructure);
    }

    void sendChar(unsigned char data)
    {
      tx_queue.push(data);
    }

    unsigned char recvChar(void)
    {
      while(USART_GetFlagStatus((USART_TypeDef *) usart_base, USART_FLAG_RXNE) == RESET);
      return USART_ReceiveData((USART_TypeDef *) usart_base);
    }

    void waitForTransmitComplete(void)
    {
      while (USART_GetFlagStatus((USART_TypeDef *) usart_base, USART_FLAG_TXE) == RESET);
      while (USART_GetFlagStatus((USART_TypeDef *) usart_base, USART_FLAG_TC) == RESET);
    }

    void waitForReceived(void)
    {
      while (USART_GetFlagStatus((USART_TypeDef *) usart_base, USART_FLAG_RXNE) == RESET);
    }

    void work(void)
    {
      while (!tx_queue.empty()) {
        while (USART_GetFlagStatus((USART_TypeDef *)usart_base, USART_FLAG_TXE) == RESET) {}
        USART_SendData((USART_TypeDef *) usart_base, tx_queue.front());
        tx_queue.pop();
      }
    }
  
    void init(UsartInterrupt* _interrupt_target)
    {
      if (rx_GPIOx != 0) {
        interrupt_target[usart_idx] = _interrupt_target;
      }
          
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
      
      GPIO_InitTypeDef GPIO_InitStructure;  
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      
      if (tx_GPIOx != 0) {
        GPIO_InitStructure.GPIO_Pin  = tx_GPIO_Pin;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init((GPIO_TypeDef *) tx_GPIOx, &GPIO_InitStructure);
      }
      
      if (rx_GPIOx != 0) {
        GPIO_InitStructure.GPIO_Pin  = rx_GPIO_Pin;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init((GPIO_TypeDef *) rx_GPIOx, &GPIO_InitStructure);
      }

      switch(usart_idx) {
        case 1:
          RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
          break;
        case 2:
          RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
          break;
        case 3:
          RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
          break;
        case 4:
          RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);
          break;
        case 5:
          RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5, ENABLE);
          break;
      }
    
      USART_InitTypeDef USART_InitStructure;
      USART_StructInit(&USART_InitStructure);
      USART_InitStructure.USART_BaudRate = baudrate;
      USART_InitStructure.USART_WordLength = USART_WordLength_8b;
      USART_InitStructure.USART_StopBits = USART_StopBits_1;
      USART_InitStructure.USART_Parity = USART_Parity_No;
      USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
      USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
      USART_Init((USART_TypeDef *) usart_base, &USART_InitStructure);
    
      USART_ITConfig((USART_TypeDef *) usart_base, USART_IT_RXNE, ENABLE);
      USART_ITConfig((USART_TypeDef *) usart_base, USART_IT_PE, ENABLE);
      USART_ITConfig((USART_TypeDef *) usart_base, USART_IT_ERR, ENABLE);
    
      USART_Cmd((USART_TypeDef *) usart_base, ENABLE);
    
      enableRxInterrupt();
    }

  private:
    static const int tx_queue_size = 2500;
    Queue<unsigned char, tx_queue_size> tx_queue;

};

#endif /* USART_H_ */
